// C 语言 setupapi，枚举串口(包括友好名称、物理设备对象名称等)
#include <windows.h>
#include <stdlib.h>
#include <stdbool.h>
#include <tchar.h>
#include <setupapi.h>
#include <initguid.h>
#include "utils.h"
#include "com.h"


#ifndef GUID_DEVINTERFACE_COMPORT
DEFINE_GUID(GUID_DEVINTERFACE_COMPORT, 0x86E0D1E0L, 0x8089, 0x11D0, 0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73);
#endif


static com_t *g_com_list[COM_NUM_MAX];
static int g_com_index = 0;


static bool test_com(char *name)
{
    bool available = false;
    HANDLE handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (handle != INVALID_HANDLE_VALUE) {
        DCB dcbSerialParams = { 0 };
        dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
        if (GetCommState(handle, &dcbSerialParams)) {
            // 获取串口状态成功，串口可用
            available = true;
        }
        CloseHandle(handle); // 关闭句柄
    }
    return available;
}

static void add_com(TCHAR *name, TCHAR *friendly_name)
{
    com_t *com = (com_t *)malloc(sizeof(com_t));
    if (com == NULL) {
        uierr("memory alloc failed !\n");
        exit(-1);
    }
    memset(com, 0, sizeof(com_t));
#ifdef UNICODE
#pragma message("platform defined: UNICODE")
    //strPortName = wstringToString(portName);
    //strFriendlyName = wstringToString(fname);
    strcpy(com->name, name);
    strcpy(com->friendly_name, friendly_name);
#else
    strcpy(com->name, name);
    strcpy(com->friendly_name, friendly_name);
#endif
    com->available = test_com(com->name);
    g_com_list[g_com_index++] = com;
}

static bool enumDetailsSerialPorts(void)
{
    // https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdienumdeviceinfo
    bool bRet = false;
    HDEVINFO hdevinfo = INVALID_HANDLE_VALUE;
    // Return only devices that are currently present in a system
    // The GUID_DEVINTERFACE_COMPORT device interface class is defined for COM ports. GUID
    // {86E0D1E0-8089-11D0-9CE4-08003E301F73}
    hdevinfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (INVALID_HANDLE_VALUE != hdevinfo) {
        SP_DEVINFO_DATA devInfoData;
        // The caller must set DeviceInfoData.cbSize to sizeof(SP_DEVINFO_DATA)
        devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
        for (DWORD i = 0; SetupDiEnumDeviceInfo(hdevinfo, i, &devInfoData); i++) {
            // get port name
            TCHAR portName[256];
            HKEY hDevKey = SetupDiOpenDevRegKey(hdevinfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
            if (INVALID_HANDLE_VALUE != hDevKey)
            {
                DWORD dwCount = 255; // DEV_NAME_MAX_LEN
                RegQueryValueEx(hDevKey, _T("PortName"), NULL, NULL, (BYTE*)portName, &dwCount);
                RegCloseKey(hDevKey);
            }
            // get friendly name
            TCHAR fname[256];
            SetupDiGetDeviceRegistryProperty(hdevinfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)fname,
                    sizeof(fname), NULL);

            add_com(portName, fname);
        }
        if (ERROR_NO_MORE_ITEMS == GetLastError()) {
            bRet = true; // no more item
        }
    }
    SetupDiDestroyDeviceInfoList(hdevinfo);
    return bRet;
}


/****************************************************************************************************/

// return: com count
int scan_com(void)
{
    if (g_com_index > 0)
        for (int i = 0; i < g_com_index; i++)
            free(g_com_list[i]);

    memset(g_com_list, 0, sizeof(g_com_list));
    g_com_index = 0;
    enumDetailsSerialPorts();
    return g_com_index;
}

com_t *get_com(int index)
{
    return g_com_list[index];
}

int open_com(com_t *com)
{
    HANDLE handle = CreateFile(com->name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (handle == INVALID_HANDLE_VALUE) {
        return -1;
    }
    com->handle = handle;
    return 0;
}

int config_com(com_t *com, com_config_t *config)
{
    HANDLE handle  = com->handle;
    DWORD baudrate = config->baudrate;
    BYTE  databits = config->databits;
    BYTE  stopbits = config->stopbits;
    BYTE  parity   = config->parity;
    DCB dcb = {0}; // dcbSerialParams
    COMMTIMEOUTS timeouts = {0};
    // 获取当前串口参数
    if (!GetCommState(handle, &dcb)) {
        printf("Failed to get current serial port parameters. Error code: %ld\n", GetLastError());
        return -1;
    }
    // 设置波特率
    dcb.BaudRate = baudrate;
    // 设置数据位
    dcb.ByteSize = databits;
    // 设置停止位
    switch (stopbits) {
        case 1: // 1 stop bit
            dcb.StopBits = ONESTOPBIT;
            break;
        case 2: // 1.5 stop bits
            dcb.StopBits = ONE5STOPBITS;
            break;
        case 3: // 2 stop bits
            dcb.StopBits = TWOSTOPBITS;
            break;
        default:
            printf("Invalid stop bits. Valid values are 1, 2, or 3.\n");
            return -2;
    }
    // 设置校验位
    switch (parity) {
        case 0: // None
            dcb.Parity = NOPARITY;
            break;
        case 1: // Odd
            dcb.Parity = ODDPARITY;
            break;
        case 2: // Even
            dcb.Parity = EVENPARITY;
            break;
        case 3: // Mark
            dcb.Parity = MARKPARITY;
            break;
        case 4: // Space
            dcb.Parity = SPACEPARITY;
            break;
        default:
            printf("Invalid parity. Valid values are 0 (None), 1 (Odd), 2 (Even), 3 (Mark), or 4 (Space).\n");
            return -3;
    }
    // 设置超时时间
    timeouts.ReadIntervalTimeout         = MAXDWORD;
    timeouts.ReadTotalTimeoutConstant    = 0;
    timeouts.ReadTotalTimeoutMultiplier  = 0;
    timeouts.WriteTotalTimeoutConstant   = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    // 应用设置
    if (!SetCommState(handle, &dcb) || !SetCommTimeouts(handle, &timeouts)) {
        printf("Failed to set serial port parameters. Error code: %ld\n", GetLastError());
        return -4;
    }
    printf("Serial port parameters set successfully.\n");
    return 0;
}

void read_com(com_t *com)
{
}

void write_com(com_t *com)
{
}

